<< & <<< & < <()
<<(Here document)
<<
被称为 here-document
结构。你让程序知道什么将会结束文本,并且无论何时见到该分隔符,程序就会读取你提供给程序的所有内容作为输入并在其上执行任务。
这里有一个例子:
$ wc << EOF > one two three > four five > EOF 2 5 24
在该例中我们告诉 wc
程序等待 EOF
字符串,然后输入五个单词,并之后输入 EOF
来通知我们已经完成了输入。实际上,这类似于单独运行 wc
,输入单词,然后按 Ctrl-D
。
在 bash 中这些通过临时文件实现,通常是 /tmp/sh-thd.<random string>
的形式,然而在 dash 中他们被实现为匿名管道。这可以通过使用 starce
命令追踪系统调用观察到。使用 sh
替换 bash
来查看 /bin/sh
如何执行此重定向。
$ strace -e open,dup2,pipe,write -f bash -c 'cat <<EOF > test > EOF'
<<<(Here string)
<<<
被称为 here-string
。代替输入文本,你将预制的文本字符串提供给程序。例如,对于诸如 bc
之类的程序,我们可以执行 bc <<< 5*4
来获取此特定情况下的输出,而不需要交互运行 bc。
here-string 在 bash 中通过临时文件实现,通常是 /tmp/sh-thd.<random string>
的形式,之后被 unlink,所以会使它们临时占用一些内存空间,但又不会出现在 /tmp
目录的列表中,并有效的以匿名文件的形式存在,仍然可以由 shell 本身通过文件描述符进行引用,并且该文件描述符被该命令继承,之后通过=dup2()=函数复制到文件描述符 0(stdin)。这可以通过以下命令观察:
$ ls -l /proc/self/fd/ <<< "TEST" total 0 lr-x------ 1 user1 user1 64 Aug 20 13:43 0 -> /tmp/sh-thd.761Lj9 (deleted) lrwx------ 1 user1 user1 64 Aug 20 13:43 1 -> /dev/pts/4 lrwx------ 1 user1 user1 64 Aug 20 13:43 2 -> /dev/pts/4 lr-x------ 1 user1 user1 64 Aug 20 13:43 3 -> /proc/10068/fd
并通过追踪系统调用(缩短输出以提高可读性;注意临时文件是如何被作为 fd 3 打开的,写入数据,然后使用=O_RDONLY=flag 作为 fd 4 重新打开并之后被 unlink,然后=dup2()=到 fd 0,其之后会被=cat=继承):
$ strace -f -e open,read,write,dup2,unlink,execve bash -c 'cat <<< "TEST"' execve("/bin/bash", ["bash", "-c", "cat <<< \"TEST\""], [/* 47 vars */]) = 0 ... strace: Process 10229 attached [pid 10229] open("/tmp/sh-thd.uhpSrD", O_RDWR|O_CREAT|O_EXCL, 0600) = 3 [pid 10229] write(3, "TEST", 4) = 4 [pid 10229] write(3, "\n", 1) = 1 [pid 10229] open("/tmp/sh-thd.uhpSrD", O_RDONLY) = 4 [pid 10229] unlink("/tmp/sh-thd.uhpSrD") = 0 [pid 10229] dup2(4, 0) = 0 [pid 10229] execve("/bin/cat", ["cat"], [/* 47 vars */]) = 0 ... [pid 10229] read(0, "TEST\n", 131072) = 5 [pid 10229] write(1, "TEST\n", 5TEST ) = 5 [pid 10229] read(0, "", 131072) = 0 [pid 10229] +++ exited with 0 +++ --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=10229, si_uid=1000, si_status=0, si_utime=0, si_stime=0} --- +++ exited with 0 +++
观点:可能因为 here-string 使用了临时文件,这是为什么 here-string 总是插入尾随换行符的可能原因,因为由 POSIX 定义的文本文件必须具有以换行符结尾的行。
< <()(Process Substitution)
如 tldp.org 的解释:
进程替换一个(或多个)进程的输出馈送到另一个进程的标准输入中。
所以这类似于将一个命令的 stdout 传递给另一个命令,例如 echo foobar barfoo | wc
。但是注意:再 bash manpage 中你会看到它表示为 <(list)
。所以基本上你可以重定向多个(!)命令的输出。
注意:从技术上讲,当你说 < <
时并不是指一件事,而是两个重定向,一个是单一的 <
以及一个 <( . . . )
输出的进程重定向。
现在如果我们仅进行进程替换会发生什么?
$ echo <(echo bar) /dev/fd/63
正如你看到的,shell 在输出所在的位置创建了临时文件描述符 /dev/fd/63
(根据 Gilles 的回答,这是一个匿名管道)。这意味着 <
将文件描述符重定向为命令的输入。
所以非常简单的例子是将两个 echo 命令的输出的进程替换到 wc
$ wc < <(echo bar;echo foo) 2 2 8
所以在这里我们让 shell 为括号中出现的所有输出创建文件描述符,并将其重定向为 wc
的输入。如期待的一样,wc 从两个 echo 命令接收该 stream,它们本身输出两行,每行有一个单词,并适当地我们有 2 个单词,2行和 6 个字符加两个换行符。
$ (echo foo;echo bar) | wc 2 2 8
实际上,着和上面的例子相同。然而,这与进程替换在表面之下是不同的,因为我们将 subshell 的整个标准输出与=wc=的标准输入使用管道链接在了一起。在另一方面,进程替换使命令读取一个临时文件描述符。
所以如果我们可以使用管道进行分组,为什么我们需要进程替换?因为有些时候我们不能使用管道。考虑以下示例
使用 diff
比较两个命令的输出(其需要两个文件,在这种情况下,我们为它提供两个文件描述符)
diff <(ls /bin) <(ls /usr/bin)